<?php
/**
 * crcl/base
 *
 * Copyright 2013 CRCL - André Sabosch - info@crcl.de
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace crcl\base\mvc;

interface IResolver
{
  /**
   * Executes command
   *
   * @param \crcl\base\mvc\ICommand $oCommand
   */
  public function execute(\crcl\base\mvc\ICommand $oCommand);
}

class Resolver implements IResolver
{
  public function execute(\crcl\base\mvc\ICommand $oCommand)
  {
    if (\crcl\base\Mvc::isAssets())
    {
      $sClass = '\crcl\base\mvc\controller\Assets';
    }
    else
    {
      $sFile = $this->getControllerFile($oCommand);
      $sClass = $oCommand->getControllerWithNamespace();

      $this->checkFile($sFile);
      $this->checkClass($sFile, $sClass);
    }

    $sAction = $oCommand->getAction();
    $sMethod = $oCommand->getMethod();

    $oController = new $sClass();
    $this->checkParent($oController, $sClass);
    $this->checkAction($oController, $sAction, $sClass, $sMethod);

    $oController->setCommand($oCommand);

    $oController->onPreAction();

    //if we redirect in pre action, we shouldnt call the main action anymore
    if ($oController->getCommand()->getStatus() !== \crcl\base\mvc\Response::STATUS_REDIRECT_PERMANENT)
    {
      $sAction = $oCommand->getActionWithMethod();
      $oController->$sAction();
      //$oController->eventPostAction();
    }

    $oCommand = $oController->getCommand();
    return $oCommand;
  }

  /** PRIVATE *************************************************************************************/

  private function getControllerFile(\crcl\base\mvc\ICommand $oCommand)
  {
    $sDirectory = \crcl\base\application\Path::getDirectory(
      \crcl\base\application\Path::DIR_CONTROLLER);

//    if ($oCommand->hasVersion())
//    {
//      $sDirectory .= $oCommand->getVersion() . '/';
//    }
    if ($oCommand->hasGroup())
    {
      $sGroup = \str_replace('-', '', $oCommand->getGroup());
      $sDirectory .= $sGroup . '/';
    }
    $sFile = $sDirectory . $oCommand->getController() . '.php';

    return $sFile;
  }

  private function checkFile($sFile)
  {
    if (!\file_exists($sFile))
    {
      throw new Exception('File does not exist',
        Exception::ERROR_RESOLVER_NOFILE, array('file' => $sFile), Exception::STATUS_404);
    }
  }

  private function checkClass($sFile, $sClass)
  {
    if (!\class_exists($sClass))
    {
      throw new Exception(
        'Class does not exist',
        Exception::ERROR_RESOLVER_NOCLASS,
        array('file' => $sFile, 'class' => $sClass));
    }
  }

  private function checkAction(controller\IBase $oController, $sAction, $sClass, $sMethod)
  {
    if (!\is_callable(array($oController, $sMethod . \ucfirst($sAction)))) //"method_exists" does not report __call methods
    {
      //get gleich zum 404, alle andere testen
      switch ($sMethod)
      {
        case 'post':
        case 'put':
        case 'patch':
        case 'delete':
        case 'options':
          $sAllow = $this->determineAllow($oController, $sAction);
          if ($sAllow)
          {
            header('Allow: ' . $sAllow);
            throw new Exception(
              'Action does not exist',
              Exception::ERROR_RESOLVER_NOACTION,
              array('action' => $sAction, 'method' => $sMethod, 'class' => $sClass),
              Exception::STATUS_405);
          }
          break;
        case 'head':
          return;
      }
      throw new Exception(
        'Action does not exist',
        Exception::ERROR_RESOLVER_NOACTION,
        array('action' => $sAction, 'class' => $sClass),
        Exception::STATUS_404);
    }
  }

  private function determineAllow($oController, $sAction)
  {
    $aAllow = array();
    $sAction = \ucfirst($sAction);

    if (\is_callable(array($oController, 'get' . $sAction))) { $aAllow[] = 'GET'; };
    if (\is_callable(array($oController, 'post' . $sAction))) { $aAllow[] = 'POST'; };
    if (\is_callable(array($oController, 'put' . $sAction))) { $aAllow[] = 'PUT'; };
    if (\is_callable(array($oController, 'patch' . $sAction))) { $aAllow[] = 'PATCH'; };
    if (\is_callable(array($oController, 'delete' . $sAction))) { $aAllow[] = 'DELETE'; };
    if (\is_callable(array($oController, 'options' . $sAction))) { $aAllow[] = 'OPTIONS'; };

    $sAllow = \implode(', ', $aAllow);
    return $sAllow;
  }

  private function checkParent($oController, $sClass)
  {
    if (!\is_subclass_of($oController, '\crcl\base\mvc\controller\IBase'))
    {
      throw new Exception(
        'Wrong parent',
        Exception::ERROR_RESOLVER_WRONGPARENT,
        array(
          'class' => $sClass,
          'parent' => \get_parent_class($oController),
          'expected' => 'Child of \crcl\base\mvc\controller\Base'));
    }
  }
}